#ifndef _CRT_SECURE_NO_WARNINGS
	#define _CRT_SECURE_NO_WARNINGS
#endif

#include <GL\glui.h>
#include <math.h>
#include <iostream>
#include <string>
#include <Windows.h>

#include "Lights.h"
#include "Textures.h"
#include "Views.h"
#include "skyboxes/SkyboxModelling.h"
#include "skyboxes/SkyboxTextures.h"
#include "boards/BoardModelling.h"
#include "boards/BoardTextures.h"
#include "pieces/PiecesModelling.h"
#include "pieces/PiecesTextures.h"
#include "frameworks/Drawer.h"
#include "../plogcon/laig2plog.h"

#define SERVER_ADDRESS "127.0.0.1"

using namespace std;

extern char game_board[BOARD_DIMENSION][BOARD_DIMENSION];
SOCKET m_socket;

// Window dimension and location
#define DIMX 1270
#define DIMY 780
#define INITIALPOS_X 0
#define INITIALPOS_Y 0

#define SKYBOX_AND_BOARDS_THEME1 1
#define SKYBOX_AND_BOARDS_THEME2 2

#define BUFSIZE 512


// picking
GLuint selectBuf[BUFSIZE];

float xy_aspect;
int window_w = DIMX;
int window_h = DIMY;

// axis
double axis_radius_begin =  0.4;
double axis_radius_end   =  0.0;
double axis_lenght       = 32.0;
int axis_nslices = 8;
int axis_nstacks = 1;

// mat1
float mat1_shininess[] = {126.0}; 
float mat1_specular[] = {0.3, 0.3, 0.3, 1.0};
float mat1_diffuse[] =  {0.7, 0.7, 0.7, 1.0};
float mat1_ambient[] =  {0.7, 0.7, 0.7, 1.0};



// Global Light
float light_ambient[] = {0.6, 0.6, 0.6, 1.0};


// Window
int main_window;

// Controls
int axis_visible = 0;
int lights_visible = 0;
int culling = 1;
int wireframe = 0;

// Class stuff
GLUI  *glui2;
GLUquadric *glQ;

// Game history
vector<board_game_typedef> game_history;
int number_play;

char *currentPlayer = "1";

int theme = SKYBOX_AND_BOARDS_THEME1;

void draw_scene(GLenum);

void nextTheme() {
	nextSkybox();
	nextMainBoard();
	nextBenchBoard();
}

/*
	###################################################################################
	#																				  #
	#								DISPLAY FUNCTIONS                                 #
	#																				  #
	###################################################################################
*/

void display_axis() {	
	if( !axis_visible )
		return;

	// X
	glColor3f(1.0,0.0,0.0);
	glPushMatrix();
	glRotated(90.0, 0.0,1.0,0.0 );
	gluCylinder(glQ, axis_radius_begin, axis_radius_end,
		             axis_lenght, axis_nslices, axis_nstacks);
	glPopMatrix();

	// Y
	glColor3f(0.0,1.0,0.0);
	glPushMatrix();
	glRotated(-90.0, 1.0,0.0,0.0 );
	gluCylinder(glQ, axis_radius_begin, axis_radius_end,
		             axis_lenght, axis_nslices, axis_nstacks);
	glPopMatrix();
	
	// Z
	glColor3f(0.0,0.0,1.0);
	glPushMatrix();
	gluCylinder(glQ, axis_radius_begin, axis_radius_end,
		             axis_lenght, axis_nslices, axis_nstacks);
	glPopMatrix();
}

void display_view() {
	cams[current_camera_idx].setCam();
}

void set_default_material() {
	glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat1_shininess);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,  mat1_specular);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE,   mat1_diffuse);
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,   mat1_ambient);
}

int time_counter = 0;
bool game_over = false;

void timerCallback (int value)
{
	time_counter++;

	if( game_over ) {
		time_counter = 0;
		game_over = false;
	}

	glutTimerFunc (1000, timerCallback, value);
}

void draw_time_counter() {
	char msg[] = "Time (secs): ";
	int length = strlen(msg);

	// Sets Projection and Modelview matrices for 2D Rendering
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
		glLoadIdentity();
		gluOrtho2D(0.0, 100.0, 0.0, 100.0);

		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
			glLoadIdentity();

			glDisable(GL_LIGHTING);
			glColor3f(1.0, 1.0, 1.0);
			glRasterPos2f(1.0, 100.0 - 10.0);
			for(int i = 0; i < length; ++i) {
				glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, msg[i]);
			}

			char time[BUFSIZE];
			itoa(time_counter, time, 10);
			
			length = strlen(time);
			for(int i = 0; i < length; ++i) {
				glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, time[i]);
			}

			
			glEnable(GL_LIGHTING);
			glMatrixMode(GL_MODELVIEW); // Resets the Matrices for 3D Rendering
		glPopMatrix();
		glMatrixMode(GL_PROJECTION);
	glPopMatrix();
}

int player1_score = 0,
	player2_score = 0;

void draw_score_counter() {
	char *msg = "Player 1: ";
	int length = strlen(msg);

	// Sets Projection and Modelview matrices for 2D Rendering
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
		glLoadIdentity();
		gluOrtho2D(0.0, 100.0, 0.0, 100.0);

		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
			glLoadIdentity();

			glDisable(GL_LIGHTING);
			glColor3f(1.0, 1.0, 1.0);
			glRasterPos2f(1.0, 100.0 - 15.0);
			for(int i = 0; i < length; ++i) {
				glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, msg[i]);
			}

			char score[BUFSIZE];
			itoa(player1_score, score, 10);
			
			length = strlen(score);
			for(int i = 0; i < length; ++i) {
				glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, score[i]);
			}
			msg = "Player 2: ";
			length = strlen(msg);
			glRasterPos2f(1.0, 100.0 -20.0);
			for(int i = 0; i < length; ++i) {
				glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, msg[i]);
			}

			itoa(player2_score, score, 10);
			
			length = strlen(score);
			for(int i = 0; i < length; ++i) {
				glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, score[i]);
			}

			
			glEnable(GL_LIGHTING);
			glMatrixMode(GL_MODELVIEW); // Resets the Matrices for 3D Rendering
		glPopMatrix();
		glMatrixMode(GL_PROJECTION);
	glPopMatrix();
}

void display(void)
{
	if( culling ) {
		glEnable(GL_CULL_FACE);
		glCullFace(GL_BACK);
	}
	else
		glDisable(GL_CULL_FACE);

	if( wireframe )
		glPolygonMode(GL_FRONT, GL_LINE);
	else
		glPolygonMode(GL_FRONT, GL_FILL);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();
	
	draw_scene(GL_RENDER);

	glutSwapBuffers();

	glFlush();
}

/*
	###################################################################################
	#																				  #
	#								EVENT  FUNCTIONS                                  #
	#																				  #
	###################################################################################
*/
void processHits(GLint hits, GLuint buffer[]);

/* Mouse handling */
void processMouse(int button, int state, int x, int y) {
    GLint hits;
	GLint viewport[4];

	if(button != GLUT_LEFT_BUTTON || state != GLUT_DOWN)
		return;

	glGetIntegerv(GL_VIEWPORT, viewport);

	glSelectBuffer (BUFSIZE, selectBuf);
	glRenderMode (GL_SELECT);

	// init picking
	glInitNames();
	glPushName(-1);
	glMatrixMode (GL_PROJECTION);
	glPushMatrix();

	glLoadIdentity ();
	gluPickMatrix ((GLdouble) x, (GLdouble) (window_h - y), 1.0, 1.0, viewport);

	draw_scene(GL_SELECT);

	glMatrixMode (GL_PROJECTION);
	glPopMatrix ();
	glFlush ();

	hits = glRenderMode(GL_RENDER);
	processHits(hits, selectBuf);
}

void processMouseMoved(int x, int y)
{
	glutPostRedisplay();
}

void processPassiveMouseMoved(int x, int y)
{
	glutPostRedisplay();				
}

void reshape(int w, int h)
{
	int tx, ty, tw, th;

	GLUI_Master.get_viewport_area( &tx, &ty, &tw, &th );
	glViewport( tx, ty, tw, th );
	xy_aspect = (float)tw / (float)th;

	glutPostRedisplay();
}


void keyboard(unsigned char key, int x, int y)
{
	switch (key) {
		case 27:
			exit(0);
			break;
		case 't':
			nextTheme();
			theme = (theme == SKYBOX_AND_BOARDS_THEME1 ? 
						SKYBOX_AND_BOARDS_THEME2 : 
						SKYBOX_AND_BOARDS_THEME1);
			break;
		case KEY_CAM1:
			current_camera_idx = CAMERA1_IDX;
			break;
		case KEY_CAM2:
			current_camera_idx = CAMERA2_IDX;
			break;
		case KEY_CAM3:
			current_camera_idx = CAMERA3_IDX;
			break;
		case KEY_CAM4:
			current_camera_idx = CAMERA4_IDX;
			break;
		/*
		case 'w':
			if (number_play != game_history.size())
				number_play++;
			break;
		case 's':
			if (number_play != 0)
				number_play--;
			break;
			*/
		default:
			break;
	}
	glui2->sync_live();
}

void myGlutIdle( void )
{
  /* According to the GLUT specification, the current window is 
     undefined during an idle callback.  So we need to explicitly change
     it if necessary */
  if ( glutGetWindow() != main_window ) 
    glutSetWindow(main_window);  


  glutPostRedisplay();
}


/*
	###################################################################################
	#																				  #
	#						          PICKING FUNCTIONS                               #
	#																				  #
	###################################################################################
*/

void draw_scene(GLenum mode) {
	glFrustum( -xy_aspect*.04, xy_aspect*.04, -.04, .04, .1, 600.0 );
	
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
	
	display_view();

	glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
	glEnable(GL_COLOR_MATERIAL);

	display_axis();
	display_all_lights_spheres();
	display_all_spotlights();

	glDisable(GL_COLOR_MATERIAL);

	set_default_material();

	glCallList(theme);
	
	draw_dummy_squares_and_pieces(glQ, mode);

	if( mode == GL_RENDER ) {
		showCamId(cams[current_camera_idx].cam_id);
		draw_time_counter();
		draw_score_counter();

		//draw_menu_message(50,50, "hello");
	}

	glPopName();
}

int last_pos[2] = {0, 0};
char last_piece = '0';
bool piece_selected = false;

#define wrong_dimensions(x, y) !(x >= 0 && x <= BOARD_DIMENSION && y >= 0 && y <= BOARD_DIMENSION)
#define wrong_answer(answer) !(answer >= 0 && answer <= 100)
#define reset_selection(x, y, c) { x = y = 0; c = '0'; }

void reset_game() {

	time_counter = 0;
	game_over = false;
	char *new_board;
	new_board = exchange(&m_socket, "initialize.\n");
	prologCharToVector(new_board);

	//game_history.push_back(game_board);

	delete[] new_board;
}

// PICKING ACTION
void pickingAction(GLuint answer) {
	int x = answer % BOARD_DIMENSION,
		y = answer / BOARD_DIMENSION;
	
	if( wrong_answer(answer) ) {
		reset_selection(last_pos[0], last_pos[1], last_piece);
		return;
	}
	//printf("answer= %d\n", answer);
	//printf("last selected: %c -> (%d, %d)\n",
		//game_board[last_pos[0]][last_pos[1]], last_pos[1], last_pos[0]);
	//printf("selected: %c -> (%d, %d)\n", game_board[y][x], x, y);
	
	if( !piece_selected && game_board[y][x] != '0'  && game_board[y][x] != '5' && game_board[y][x] != '6') {
		last_pos[0] = y;
		last_pos[1] = x;
		last_piece = game_board[y][x];
		piece_selected = true;
	}
	else if( piece_selected ) {

		//printf("current cam: %d\n", current_camera_idx);
		
		
		if ((current_camera_idx + 1) == 1) {
			last_pos[0] = 9 - last_pos[0];
			last_pos[1] = 9 - last_pos[1];
			y = 9 - y;
			x = 9 - x;
			//puts("after mod:");
			//printf("last y: %d  last x: %d\n", last_pos[0], last_pos[1]);
			//printf("y: %d  x: %d\n", y, x);
		}


		char *new_play = createPlayCommand(last_pos[0], last_pos[1], y, x, current_camera_idx + 1); 
		char *new_board = executePlay(&m_socket, game_board, new_play);
		//printf("\n %s \n", new_board);
		if (strlen(new_board) > 10) {
			//puts("entered");
			prologCharToVector(new_board);

			/*
			game_history.push_back(game_board);
			number_play++;
			*/

			current_camera_idx = (current_camera_idx == CAMERA1_IDX ? CAMERA2_IDX : CAMERA1_IDX);

			char *check;
			check = checkEnd(&m_socket, game_board, currentPlayer);
			if ( check[0] == '1' || check[0] == '2') {
				
				puts("end_game");
				game_over = true;
				player1_score = ('1' == currentPlayer[0] ? player1_score + 1 : player1_score);
				player2_score = ('2' == currentPlayer[0] ? player2_score + 1 : player2_score);

				// show player has won w/ bitmap
				//char c = getchar();
				reset_game();
				currentPlayer = "2"; // so that the next player is 1
				current_camera_idx = CAMERA1_IDX;
			}
			delete[] check;
			currentPlayer = ( currentPlayer == "1" ? "2" : "1");
		}
		else {
			printf("invalid play\n");
		}
		//delete[] new_board;
		//delete[] new_play;

		piece_selected = false;
		reset_selection(last_pos[0], last_pos[1], last_piece);
	}
}

void processHits (GLint hits, GLuint buffer[]) {
	GLuint *ptr = buffer;
	GLuint mindepth = 0xFFFFFFFF;
	GLuint *answer=NULL;
	GLuint nn;

	for (int i = 0; i < hits; i++) {
		int num = *ptr; ptr++;
		GLuint z1 = *ptr; ptr++;
		ptr++;
		if (z1 < mindepth && num > 0) {
			mindepth = z1;
			answer = ptr;
			nn=num;
		}
		for (int j = 0; j < num; j++) 
			ptr++;
	}
	
	if (answer != NULL) 
		pickingAction(*answer);
	
}

/*
	###################################################################################
	#																				  #
	#						       INITIALIZATION FUNCTIONS                           #
	#																				  #
	###################################################################################
*/

void inicializacao()
{
	glQ = gluNewQuadric();

	glFrontFace(GL_CCW);		// Front faces defined using a counterclockwise rotation
	glDepthFunc(GL_LEQUAL);
	glEnable(GL_DEPTH_TEST);	// Use a depth (z) buffer to draw only visible objects

	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, light_ambient);
	glLightModelf (GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
	glLightModeli (GL_LIGHT_MODEL_LOCAL_VIEWER, 1);

	init_all_board_lights();
	
	glEnable(GL_LIGHTING);

	// change me to lights
	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHT1); 
	glEnable(GL_LIGHT2);
	glEnable(GL_LIGHT3);
	glEnable(GL_LIGHT4);
	glEnable(GL_LIGHT5);
	glEnable(GL_LIGHT6);
	glEnable(GL_LIGHT7);

	loadTextures();
	
	nextTheme();
	
	glNewList(SKYBOX_AND_BOARDS_THEME2, GL_COMPILE);
		display_skybox();
		display_boards(glQ);
	glEndList();
	
	nextTheme();

	glNewList(SKYBOX_AND_BOARDS_THEME1, GL_COMPILE);
		display_skybox();
		display_boards(glQ);
	glEndList();
	
	glutTimerFunc (1000, timerCallback, 0);

	glShadeModel(GL_SMOOTH);
}

void terminacao()
{
	gluDeleteQuadric(glQ);
}



int main(int argc, char* argv[])
{
	puts("port?");
	char serverPort[64];
	scanf("%s",serverPort);

	if (!socketConnect(&m_socket, SERVER_ADDRESS, serverPort))
		return -1;
	
	reset_game();
	
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowSize (DIMX, DIMY);
	glutInitWindowPosition (INITIALPOS_X, INITIALPOS_Y);
	main_window = glutCreateWindow (argv[0]);
 
	glutDisplayFunc(display);
	GLUI_Master.set_glutReshapeFunc(reshape);
	GLUI_Master.set_glutKeyboardFunc (keyboard);
	GLUI_Master.set_glutMouseFunc(processMouse);
	glutMotionFunc(processMouseMoved);
	glutPassiveMotionFunc(processPassiveMouseMoved);
	GLUI_Master.set_glutSpecialFunc( NULL );
   
	/*** Create the bottom subwindow ***/
	glui2 = GLUI_Master.create_glui_subwindow( main_window, GLUI_SUBWINDOW_BOTTOM );
	glui2->set_main_gfx_window( main_window );

	GLUI_Rotation *view_rot = glui2->add_rotation( "Rotate", view_rotate );
	view_rot->set_spin( 1.0 );
	glui2->add_column( false );
	GLUI_Translation *trans_z = glui2->add_translation( "Zoom", GLUI_TRANSLATION_Z, &obj_pos[2] );
	trans_z->set_speed( .10 );
	/*
	glui2->add_column( true );
	glui2->add_checkbox ( "Axis", &axis_visible );
	glui2->add_checkbox ( "Light's Sphere", &lights_visible);
	glui2->add_checkbox ( "Culling", &culling);
	glui2->add_checkbox ( "Wireframe", &wireframe);
	*/
	glui2->add_column( true );
	GLUI_Listbox *themes_listbox = glui2->add_listbox("Themes", &theme);
	themes_listbox->add_item (SKYBOX_AND_BOARDS_THEME1, "Stormy Days");
	themes_listbox->add_item (SKYBOX_AND_BOARDS_THEME2, "Winter");
	themes_listbox->set_int_val(SKYBOX_AND_BOARDS_THEME1);

	GLUI_Listbox *views_listbox = glui2->add_listbox("Views", &current_camera_idx);
	views_listbox->add_item(CAMERA1_IDX, cams[CAMERA1_IDX].cam_id);
	views_listbox->add_item(CAMERA2_IDX, cams[CAMERA2_IDX].cam_id);
	views_listbox->add_item(CAMERA3_IDX, cams[CAMERA3_IDX].cam_id);
	views_listbox->add_item(CAMERA4_IDX, cams[CAMERA4_IDX].cam_id);
	

	/* We register the idle callback with GLUI, not with GLUT */
	GLUI_Master.set_glutIdleFunc( myGlutIdle );
   
	inicializacao();
	
	glutMainLoop();

	terminacao();

	return 0;
}